# -*- coding: utf-8 -*-

import datetime
import logging
import os

from PyQt5.QtCore import Qt
from PyQt5.QtGui import QTextCursor, QIcon
from PyQt5.QtWidgets import (QPushButton, QLineEdit,
                             QAbstractItemView, QMenuBar,
                             QInputDialog, QMessageBox, QGridLayout,
                             QApplication, QVBoxLayout, QSplitter, QWidget, QTextBrowser, QTabWidget,
                             QMenu, QAction, QDialog, QFileDialog, QGroupBox, QFrame, QTableWidget, QTableWidgetItem,
                             QHeaderView, QHBoxLayout)

from comp_cab_setup_modules.config import version, config_path
from comp_cab_setup_modules.design import DialogForCopyStudentArchiveFromServer, WindowForSavingProfile, \
    WindowForSelectPageInBrowser, users_admins_generator
from comp_cab_setup_modules.help import HelpWindow
from comp_cab_setup_modules.hosts import Hosts
from comp_cab_setup_modules.settings_window import SettingsWindow
from comp_cab_setup_modules.system import CompKabSolutionWindow, run_command
from comp_cab_setup_modules.workers import GetWorks, CleanWorks, OpenSFTP, TasksCopyFiles, TasksClean, \
    CreateWorksFolderThread, OpenBrowserWithPage, OpenWorksFolderThread, ArchiveStudent, RestoreStudentFromArchive, \
    CopyStudentArchiveFromServer, reset_log_to_last_week, TasksCopyFolder, ShowHostsNames, SetDesktopBackground, \
    LoginStudents, SSHCommandInThreads, EndSession


class TeacherWindow(CompKabSolutionWindow):
    def __init__(self, app: QApplication):
        super().__init__()
        self.hosts = Hosts()
        self.app = app
        self.help_window = HelpWindow()

        run_command(f'touch {config_path}/ccs.conf')

        menu_bar = QMenuBar()
        menu_file = menu_bar.addMenu('Меню')
        action_help = menu_file.addAction('Справка')
        action_exit = menu_file.addAction('Выход')

        action_help.triggered.connect(self.help)
        action_exit.triggered.connect(self.close)

        self.tab1 = QWidget()
        vboxlayout = QVBoxLayout()
        self.tab1.setLayout(vboxlayout)
        self.tab2 = SettingsWindow()

        self.tabs = QTabWidget()
        self.tabs.addTab(self.tab1, "Учителю")
        self.tabs.addTab(self.tab2, "Настройка хостов")
        all_window_layout = QVBoxLayout()
        all_window_layout.addWidget(self.tabs)
        self.setLayout(all_window_layout)
        self.tabs.currentChanged.connect(self.update_hosts_list)

        self.tabs.currentChanged.connect(self.settings_tab_restriction_check)

        splitter = QSplitter()
        splitter.setOrientation(Qt.Vertical)
        vboxlayout.addWidget(splitter)

        all_window_layout.setMenuBar(menu_bar)

        upper_widget = QWidget()
        grid = QGridLayout()
        upper_widget.setLayout(grid)

        works_and_tasks_groupbox = QGroupBox('Работы и задания')
        works_and_tasks_groupbox_layout = QVBoxLayout()
        works_and_tasks_groupbox.setLayout(works_and_tasks_groupbox_layout)
        archive_and_restore_groupbox = QGroupBox('Архивация и восстановление')
        archive_and_restore_groupbox_layout = QVBoxLayout()
        archive_and_restore_groupbox.setLayout(archive_and_restore_groupbox_layout)
        on_hosts_groupbox = QGroupBox('Действия на хостах')
        on_hosts_groupbox_layout = QVBoxLayout()
        on_hosts_groupbox.setLayout(on_hosts_groupbox_layout)
        on_server_groupbox = QGroupBox('Действия на сервере')
        on_server_groupbox_layout = QVBoxLayout()
        on_server_groupbox.setLayout(on_server_groupbox_layout)

        names = [
            "Создать папку Сдать работы",
            'Собрать работы',
            'Очистить работы',
            'Раздать задания',
            'Очистить задания',
            # 'Пересоздать учётную запись ученика',
            'Открыть проводник',
            "Открыть папку Работы",
            'Отобразить имя',
            'Поменять обои',
            "Открыть браузер",
            "Удалённая авторизация учеников",
            "Удалённый выход из системы",
            "Перезагрузить",
            "Архивировать /home",
            "Восстановить /home",
            # "Архивировать /home на сервере",
            "Копировать архив /home с сервера"
        ]

        functions = [
            self.create_works_folder,
            self.get_works,
            self.clean_works,
            self.show_copy_tasks_menu,
            self.clean_tasks,
            # self.backup_student,
            # self.delete_student,
            self.open_sftp,
            self.open_works_folder,
            self.display_name,
            self.change_wallpapers,
            self.open_browser,
            self.login_students,
            # тут было и, возможно, будет контекстное меню self.end_session
            # если получится реализовать блокировку сеанса без потерей хостов вейоном
            # и периодического зависания сессии, требующего полной перезагрузки
            self.on_end_session_menu_elem_select,
            self.reboot_hosts,
            self.archive_student,
            self.restore_student_from_archive,
            # self.archive_student_on_server,
            self.restore_student_from_archive_on_server
        ]

        hints = (
            "Создаёт на рабочем столе ученика на выбранных компьютерах папку Сдать работы",
            "Копирует работы из папки Сдать работы на рабочем столе каждого ученика в папку Работы на рабочем столе "
            "учителя",
            "Очищает папку Сдать работы на рабочем столе каждого ученика",
            "Копирует выбранные файлы в папку Задания на Рабочем столе ученика",
            "Очищает папку Задания на Рабочем столе ученика",
            "Открывает домашнюю папку ученика на сервере",
            "Открывает папку Работы на Рабочем столе учителя",
            "Отображает имя хоста на его экране",
            "Меняет обои у учеников",
            "Открывает у учеников браузер с указанной страницей",
            "Осуществляет вход в систему указанных в таблице пользователей",
            "Завершает сеанс пользователя на выбранных хостах",
            "Перезагружает выбранные компьютеры",
            "Создаёт архив /home ученика",
            "Восстанавливает /home ученика из архива",
            # "Создаёт архив /home ученика на компьютере учителя",
            "Создаёт и копирует архив /home ученика с компьютера учителя для последующего восстановления"
        )

        icons = (
            'folder-new',
            'document-import',
            'edit-delete',
            'edit-copy',
            'edit-delete-shred',

            'document-open-folder',
            'user-desktop',

            'im-user',
            'preferences-desktop-display-randr',
            'folder-html',
            'user-others',
            'im-invisible-user',
            'view-refresh',

            'archive-insert',
            'archive-extract',
            'edit-copy-path'
        )

        for i in range(len(names)):
            button = QPushButton(names[i])
            button.clicked.connect(functions[i])
            button.setToolTip(hints[i])
            button.setIcon(QIcon.fromTheme(icons[i]))
            if i in (0, 1, 2, 3, 4):
                works_and_tasks_groupbox_layout.addWidget(button)
                if i == 2:
                    line = QFrame()
                    line.setFrameShape(QFrame.HLine)
                    works_and_tasks_groupbox_layout.addWidget(line)
                    line.setDisabled(True)
            elif i in (5, 6):
                on_server_groupbox_layout.addWidget(button)
            elif i in (7, 8, 9, 10, 11, 12):
                on_hosts_groupbox_layout.addWidget(button)
            else:
                archive_and_restore_groupbox_layout.addWidget(button)

        for layout in (
                works_and_tasks_groupbox_layout,
                archive_and_restore_groupbox_layout,
                on_hosts_groupbox_layout,
                on_server_groupbox_layout
        ):
            layout.setAlignment(Qt.AlignTop)
        actions_layout = QHBoxLayout()

        actions_layout.addWidget(works_and_tasks_groupbox)
        actions_layout.addWidget(archive_and_restore_groupbox)
        actions_layout.addWidget(on_hosts_groupbox)
        actions_layout.addWidget(on_server_groupbox)
        actions_widget = QWidget()
        actions_widget.setLayout(actions_layout)

        button = QPushButton('Выбрать всё')
        button.clicked.connect(self.select_all)
        grid.addWidget(button, 1, 0)
        button = QPushButton('Очистить выбор')
        button.clicked.connect(self.select_none)
        grid.addWidget(button, 1, 1)

        separator = QFrame()
        separator.setFrameShape(QFrame.VLine)
        separator.setDisabled(True)
        grid.addWidget(separator, 1, 2)

        button = QPushButton('Генерация пользователей')
        button.clicked.connect(self.generate_students)
        grid.addWidget(button, 1, 3)

        separator = QFrame()
        separator.setFrameShape(QFrame.VLine)
        separator.setDisabled(True)
        grid.addWidget(separator, 1, 4)

        button = QPushButton('Сохранить профиль...')
        button.clicked.connect(self.save_profile)
        grid.addWidget(button, 1, 5)
        button = QPushButton('Перейти к профилю...')
        button.clicked.connect(self.load_profile)
        grid.addWidget(button, 1, 6)

        self.hosts_table = QTableWidget()
        self.hosts_table.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.hosts_table.setColumnCount(3)

        self.hosts_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
        self.hosts_table.horizontalHeader().setStretchLastSection(True)
        self.hosts_table.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)

        self.hosts_table.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.hosts_table.verticalHeader().setSectionResizeMode(QHeaderView.Fixed)
        self.hosts_table.itemChanged.connect(self.change_data)

        self.load_hosts_to_table()
        grid.addWidget(self.hosts_table, 2, 0, 1, 7)

        self.textfield = QTextBrowser()
        self.textfield.setAcceptRichText(True)
        self.textfield.cursor = QTextCursor()
        self.textfield.setReadOnly(True)
        self.textfield.setOpenExternalLinks(True)

        actions_and_textfield_layout = QVBoxLayout()
        actions_and_textfield_layout.addWidget(actions_widget)
        actions_and_textfield_layout.addWidget(self.textfield)
        actions_and_textfield_widget = QWidget()
        actions_and_textfield_widget.setLayout(actions_and_textfield_layout)

        splitter.addWidget(upper_widget)
        splitter.addWidget(actions_and_textfield_widget)

        x_coord = run_command(f'py-ini-config get {config_path}/ccs.conf Main xcoord')
        x_coord = int(x_coord) if x_coord else 300
        y_coord = run_command(f'py-ini-config get {config_path}/ccs.conf Main ycoord')
        y_coord = int(y_coord) if y_coord else 150
        window_width = run_command(f'py-ini-config get {config_path}/ccs.conf Main windowwidth')
        window_height = run_command(f'py-ini-config get {config_path}/ccs.conf Main windowheight')
        window_width = int(window_width) if window_width else 800
        window_height = int(window_height) if window_height else 500
        self.setGeometry(x_coord, y_coord, window_width, window_height)
        self.setWindowTitle(f'Управление компьютерным кабинетом, версия {version}')

        self.show()

        all_columns = sum([self.hosts_table.columnWidth(i) for i in range(3)])
        for i in range(3):
            self.hosts_table.setColumnWidth(i, all_columns // 3)
        reset_log_to_last_week(f"{config_path}/log.txt")

    def load_hosts_to_table(self) -> None:
        """
        Загружает в таблицу список хостов с логинами и паролями учеников из файла
        """
        self.hosts_table.setHorizontalHeaderLabels([
            'Имя хоста',
            'Логин ученика',
            'Пароль ученика'
        ])
        hosts_from_file = self.hosts.to_list()
        if not hosts_from_file:
            self.help()
        else:
            self.hosts_table.setRowCount(len(hosts_from_file))
            for row, host in enumerate(self.hosts.hosts):
                self.hosts_table.setItem(row, 0, QTableWidgetItem(self.hosts[host].name))
                self.hosts_table.item(row, 0).setFlags(self.hosts_table.item(row, 0).flags() ^ Qt.ItemIsEditable)
                self.hosts_table.setItem(row, 1, QTableWidgetItem(self.hosts[host].student_login))
                self.hosts_table.blockSignals(True)
                self.hosts_table.setItem(row, 2, QTableWidgetItem('*' * len(self.hosts[host].student_pass)))
                self.hosts_table.item(row, 2).setToolTip(self.hosts[host].student_pass)
                self.hosts_table.item(row, 2).setData(Qt.UserRole, self.hosts[host].student_pass)
                self.hosts_table.blockSignals(False)

    def change_data(self, table_item: QTableWidgetItem) -> None:
        """
        Реагирует на изменение данных в таблице
        @param table_item: изменённая ячейка
        """
        row = table_item.row()
        column = table_item.column()
        hostname = self.hosts_table.item(row, 0).text()
        if column == 1:
            self.hosts.set_student_login(hostname, table_item.text())
        elif column == 2:
            self.hosts_table.blockSignals(True)
            self.hosts.set_student_pass(hostname, table_item.text())
            table_item.setToolTip(table_item.text())
            table_item.setData(Qt.UserRole, table_item.text())
            table_item.setText('*' * len(table_item.text()))
            self.hosts_table.blockSignals(False)

    def generate_students(self) -> None:
        """
        Заполняет логины и пароли учеников на выбранных хостах указанными значениями
        """
        get_data_window = users_admins_generator(False)
        if get_data_window.exec() == QDialog.Accepted:
            name, passwd = get_data_window.name.text(), get_data_window.password.text()
            for row in range(self.hosts_table.rowCount()):
                if self.hosts_table.item(row, 1):
                    self.hosts_table.item(row, 1).setText(name)
                else:
                    self.hosts_table.setItem(row, 1, QTableWidgetItem(name))
                if self.hosts_table.item(row, 2):
                    self.hosts_table.item(row, 2).setText(passwd)
                else:
                    self.hosts_table.setItem(row, 2, QTableWidgetItem(passwd))

    def settings_tab_restriction_check(self) -> None:
        """
        Проверка защищённости паролем вкладки настроек
        """
        if self.tabs.currentIndex() == 1 and run_command(
                f'py-ini-config get {config_path}/ccs.conf Main protect_settings').strip() == 'true':
            passwd, okPressed = QInputDialog.getText(self, "Введите пароль",
                                                     "Настройки хостов защищены паролем. Введите пароль:",
                                                     QLineEdit.Password, "")
            if not okPressed:
                self.tabs.setCurrentIndex(0)
            else:
                right_passwd_md5 = run_command(f'cat {config_path}/password_md5').strip()
                if right_passwd_md5 != run_command(f'echo -n "{passwd}" | md5sum').strip():
                    dlg = QMessageBox()
                    dlg.setWindowTitle("Ошибка")
                    dlg.setText("Введённый пароль не является корректным.")
                    dlg.exec()
                    self.tabs.setCurrentIndex(0)

    def get_selected_items_with_confirm(self) -> list:
        """
        Возвращает список объектов Host, выделенных мышью
        @return: [Host]
        """
        selected_items = self.hosts_table.selectedItems()
        names = []
        items = []
        for item in selected_items:
            names.append(item.text())
        for host in self.hosts.items_to_list():
            if host.name in names:
                items.append(host)
        if not items:
            dlg = QMessageBox(self)
            dlg.setWindowTitle("Ошибка")
            dlg.setText("Выберите хотя бы один компьютер из списка")
            button = dlg.exec()
            if button == QMessageBox.Ok:
                return None
        return items

    def update_hosts_list(self) -> None:
        """
        Обновляет список устройств в основном окне программы
        """
        self.hosts_table.blockSignals(True)

        self.hosts = Hosts()
        self.hosts_table.clear()
        self.load_hosts_to_table()
        self.hosts_table.setHorizontalHeaderLabels([
            'Имя хоста',
            'Логин ученика',
            'Пароль ученика'
        ])
        logging.info("Обновлен список устройств в QListWidget")
        self.hosts_table.blockSignals(False)

    def select_all(self) -> None:
        """
        Выделяет все устройства в основном окне программы
        """
        self.hosts_table.selectAll()

    def select_none(self) -> None:
        """
        Снимает выделение со всех устройств в основном окне программы
        """
        self.hosts_table.clearSelection()

    def update_textfield(self, message: str) -> None:
        """
        Обновляет текстовое поле в нижней части основного окна программы
        @param message: сообщение для добавления в текстовое поле
        """
        self.textfield.append(message)

    def save_profile(self) -> None:
        """
        Сохраняет текущий профиль (список хостов) в файл
        """
        window_for_getting_filename = WindowForSavingProfile()
        if window_for_getting_filename.exec():
            profile_name = window_for_getting_filename.combobox.currentText()
            if profile_name.strip():
                current_home_folder = run_command('xdg-user-dir').strip()
                run_command(f'mkdir -p {current_home_folder}/.teacher_control/profiles')
                filename = f'{current_home_folder}/.teacher_control/profiles/{window_for_getting_filename.combobox.currentText()}.json'
                self.tab2.export_json(filename)
                logging.info(f'Сохранён профиль {filename}')
                self.update_textfield(f'Профиль {filename} сохранён.')
            else:
                dlg = QMessageBox()
                dlg.setWindowTitle('Внимание!')
                dlg.setText('Имя профиля не введено.')
                dlg.exec()

    def load_profile(self) -> None:
        """
        Загружает профиль (список хостов) из файла
        @return: None
        """
        try:
            elements = []
            current_home_folder = run_command('xdg-user-dir').strip()
            for file in os.listdir(f'{current_home_folder}/.teacher_control/profiles'):
                if file.endswith('.json'):
                    elements.append(file[:-5])
            assert len(elements) > 0
        except Exception as e:
            self.update_textfield(f'Не найдены сохранённые профили')
            logging.info(f'Не найдены сохранённые профили, {e}')
            return
        self.profiles_menu = QMenu()
        for elem in elements:
            text_action = QAction(elem, self)
            text_action.triggered.connect(self.on_load_profile_menu_elem_select)
            self.profiles_menu.addAction(text_action)
        self.profiles_menu.exec(self.cursor().pos())
        self.update_hosts_list()

    def on_load_profile_menu_elem_select(self) -> None:
        """
        Загрузка всех сохранённых профилей
        """
        current_home_folder = run_command('xdg-user-dir').strip()
        self.tab2.import_from_json(f'{current_home_folder}/.teacher_control/profiles/{self.sender().text()}.json', True)

    def get_works(self) -> None:
        """
        Копирует работы из папок Сдать работы на Рабочем столе учеников
        с выбранных компьютеров в папку Работы на Рабочем столе учителя
        @return: None
        """
        self.textfield.clear()
        comps = self.get_selected_items_with_confirm()
        if comps:
            date = str(datetime.datetime.now().date())
            user_folder = True
            if not run_command(
                    f'py-ini-config get {config_path}/ccs.conf Main NoUserFolderInGetWorks').strip() == 'true':
                text, okPressed = QInputDialog.getText(self, "Введите название", "Название папки:", QLineEdit.Normal,
                                                       "")
            else:
                user_folder = False
                text = ''
            if not user_folder or (okPressed and text):
                self.thread = GetWorks()
                self.thread.hosts_list = comps
                self.thread.date = date
                self.thread.text = text
                self.thread.user_folder = user_folder

                self.thread.start_signal.connect(self.update_textfield)
                self.thread.progress_signal.connect(self.update_textfield)
                self.thread.finish_signal.connect(self.update_textfield)
                self.thread.finished.connect(self.thread.deleteLater)

                self.thread.start()

            elif okPressed and not text:
                dlg = QMessageBox(self)
                dlg.setWindowTitle("Ошибка")
                dlg.setText("Необходимо ввести название")
                button = dlg.exec()
                if button == QMessageBox.Ok:
                    return
            else:
                return
        else:
            return

    def clean_works(self) -> None:
        """
        Очищает папки Сдать работы на Рабочем столе учеников
        на выбранных компьютерах
        """
        self.textfield.clear()
        comps = self.get_selected_items_with_confirm()
        if comps:
            self.thread = CleanWorks()
            self.thread.hosts_list = comps

            self.thread.start_signal.connect(self.update_textfield)
            self.thread.progress_signal.connect(self.update_textfield)
            self.thread.finish_signal.connect(self.update_textfield)
            self.thread.finished.connect(self.thread.deleteLater)

            self.thread.start()

    def show_copy_tasks_menu(self) -> None:
        """
        Показывает контекстное меню при импорте списка хостов
        """
        self.textfield.clear()
        import_menu = QMenu()
        text_action = QAction("Раздать файлы")
        json_action = QAction("Раздать папку")
        text_action.triggered.connect(self.copy_tasks_file)
        json_action.triggered.connect(self.copy_tasks_folder)
        import_menu.addAction(text_action)
        import_menu.addAction(json_action)
        import_menu.exec(self.cursor().pos())

    def copy_tasks_file(self) -> None:
        """
        Копирует выбранные в диалоговом окне файлы с компьютера учителя
        в папки Задания на Рабочем столе учеников на выбранных компьютерах
        """
        comps = self.get_selected_items_with_confirm()
        if comps:
            files = run_command('kdialog --getopenfilename --multiple --separate-output '
                                '--title="Выберите файлы"').strip()
            if files:
                self.thread = TasksCopyFiles()
                self.thread.host_list = comps
                self.thread.files_list = files.split('\n')
                self.thread.start_signal.connect(self.update_textfield)
                self.thread.progress_signal.connect(self.update_textfield)
                self.thread.finish_signal.connect(self.update_textfield)
                self.thread.finished.connect(self.thread.deleteLater)

                self.thread.start()

    def copy_tasks_folder(self) -> None:
        """
        Копирует выбранную в диалоговом окне папку с компьютера учителя
        в папки Задания на Рабочем столе учеников на выбранных компьютерах
        """
        comps = self.get_selected_items_with_confirm()
        if comps:
            folder = run_command('kdialog --getexistingdirectory --title="Выберите папку"').strip()
            if folder:
                self.thread = TasksCopyFolder()
                self.thread.host_list = comps
                self.thread.folder_to_copy = folder
                self.thread.start_signal.connect(self.update_textfield)
                self.thread.progress_signal.connect(self.update_textfield)
                self.thread.finish_signal.connect(self.update_textfield)
                self.thread.finished.connect(self.thread.deleteLater)

                self.thread.start()

    def clean_tasks(self) -> None:
        """
        Очищает папки Задания на Рабочем столе учеников на выбранных компьютерах
        """
        comps = self.get_selected_items_with_confirm()
        if comps:
            self.thread = TasksClean()
            self.thread.host_list = comps
            self.thread.start_signal.connect(self.update_textfield)
            self.thread.progress_signal.connect(self.update_textfield)
            self.thread.finish_signal.connect(self.update_textfield)
            self.thread.finished.connect(self.thread.deleteLater)

            self.thread.start()

    # def backup_student(self) -> None:
    #     """
    #     Создаёт заново учётные записи учеников на выбранных компьютерах
    #     @return: None
    #     """
    #     self.textfield.clear()
    #     comps = self.get_selected_items_with_confirm()
    #     if comps:
    #         # student_pass, okPressed = QInputDialog.getText(
    #         #     self, "Определите пароль", "Пароль для student:", QLineEdit.Password, ""
    #         # )
    #         # if okPressed and student_pass:
    #         self.thread = RecreateStudent()
    #         self.thread.hosts_list = comps
    #         # self.thread.student_pass = student_pass
    #
    #         self.thread.start_signal.connect(self.update_textfield)
    #         self.thread.progress_signal.connect(self.update_textfield)
    #         self.thread.finish_signal.connect(self.update_textfield)
    #         self.thread.finished.connect(self.thread.deleteLater)
    #
    #         self.thread.start()
    #         # elif okPressed and not student_pass:
    #         #     dlg = QMessageBox(self)
    #         #     dlg.setWindowTitle("Ошибка")
    #         #     dlg.setText("Необходимо ввести пароль для учётной записи student")
    #         #     button = dlg.exec()
    #         #     if button == QMessageBox.Ok:
    #         #         return
    #     else:
    #         return

    def open_sftp(self) -> None:
        """
        Открывает домашнюю папку ученика на выбранном компьютере в Dolphin
        @return: None
        """
        self.textfield.clear()
        comps = self.get_selected_items_with_confirm()
        if comps:
            if len(comps) == 1:
                self.thread = OpenSFTP()
                self.thread.hosts_list = comps

                self.thread.start_signal.connect(self.update_textfield)
                self.thread.progress_signal.connect(self.update_textfield)
                self.thread.finish_signal.connect(self.update_textfield)
                self.thread.finished.connect(self.thread.deleteLater)

                self.thread.start()
            else:
                dlg = QMessageBox(self)
                dlg.setWindowTitle("Ошибка")
                dlg.setText("Выберите ровно один компьютер из списка")
                button = dlg.exec()
                if button == QMessageBox.Ok:
                    return

    def display_name(self) -> None:
        """
        Отображает названия выбранных компьютеров на их мониторах
        """
        comps = self.get_selected_items_with_confirm()
        if comps:
            self.thread = ShowHostsNames()
            self.thread.host_list = comps
            self.thread.start_signal.connect(self.update_textfield)
            self.thread.progress_signal.connect(self.update_textfield)
            self.thread.finish_signal.connect(self.update_textfield)
            self.thread.finished.connect(self.thread.deleteLater)
            self.thread.start()

    def create_works_folder(self) -> None:
        """
        Создаёт папку Сдать работы на Рабочем столе учеников на выбранных компьютерах
        @return: None
        """
        self.textfield.clear()
        comps = self.get_selected_items_with_confirm()
        if comps:
            self.thread = CreateWorksFolderThread()
            self.thread.host_list = comps

            self.thread.start_signal.connect(self.update_textfield)
            self.thread.progress_signal.connect(self.update_textfield)
            self.thread.finish_signal.connect(self.update_textfield)
            self.thread.finished.connect(self.thread.deleteLater)

            self.thread.start()

    def open_works_folder(self) -> None:
        """
        Открывает папку Работы на сервере
        """
        self.textfield.clear()
        self.thread = OpenWorksFolderThread()
        self.thread.finish_signal.connect(self.update_textfield)
        self.thread.progress_signal.connect(self.update_textfield)
        self.thread.finished.connect(self.thread.deleteLater)
        self.thread.start()

    def open_browser(self) -> None:
        """
        Открывает браузер с указанной страницей на выбранных компьютерах
        @return: None
        """
        self.textfield.clear()
        comps = self.get_selected_items_with_confirm()
        if comps:
            self.thread = OpenBrowserWithPage()
            self.thread.host_list = comps
            self.thread.start_signal.connect(self.update_textfield)
            self.thread.progress_signal.connect(self.update_textfield)
            self.thread.finish_signal.connect(self.update_textfield)
            self.thread.finished.connect(self.thread.deleteLater)
            select_page_window = WindowForSelectPageInBrowser()
            event = select_page_window.exec()

            if event == QDialog.Accepted:
                self.thread.page = select_page_window.chosen_url
                self.thread.open_incognito = select_page_window.open_incognito_checkbox.isChecked()
                self.thread.open_fullscreen = select_page_window.open_fullscreen_checkbox.isChecked()
                self.thread.start()
            else:
                return

    def archive_student(self) -> None:
        """
        Создаёт архивы домашних папок учеников на выбранных компьютерах
        """
        self.textfield.clear()
        comps = self.get_selected_items_with_confirm()
        if comps:
            self.thread = ArchiveStudent()
            self.thread.host_list = comps

            self.thread.start_signal.connect(self.update_textfield)
            self.thread.progress_signal.connect(self.update_textfield)
            self.thread.finish_signal.connect(self.update_textfield)
            self.thread.finished.connect(self.thread.deleteLater)

            self.thread.start()

    def restore_student_from_archive(self) -> None:
        """
        Восстанавливает домашние папки учеников на выбранных компьютерах из существующих архивов
        """
        self.textfield.clear()
        comps = self.get_selected_items_with_confirm()
        if comps:
            self.thread = RestoreStudentFromArchive()
            self.thread.host_list = comps

            self.thread.start_signal.connect(self.update_textfield)
            self.thread.progress_signal.connect(self.update_textfield)
            self.thread.finish_signal.connect(self.update_textfield)
            self.thread.finished.connect(self.thread.deleteLater)

            self.thread.start()

    # def archive_student_on_server(self):
    #     archive_window = DialogForArchiveStudentOnServer()
    #     archive_window.exec()

    def restore_student_from_archive_on_server(self) -> None:
        """
        Восстанавливает домашние папки учеников на выбранных хостах из архива на сервере
        """
        copy_archive_window = DialogForCopyStudentArchiveFromServer()
        if copy_archive_window.exec():
            self.textfield.clear()
            comps = self.get_selected_items_with_confirm()
            if comps:
                self.thread = CopyStudentArchiveFromServer()
                self.thread.host_list = comps
                self.thread.login = copy_archive_window.comboBox.currentText()

                self.thread.start_signal.connect(self.update_textfield)
                self.thread.progress_signal.connect(self.update_textfield)
                self.thread.finish_signal.connect(self.update_textfield)
                self.thread.finished.connect(self.thread.deleteLater)

                self.thread.start()

    def change_wallpapers(self) -> None:
        """
        Меняет обои на хостах на выбранный учителем файл
        """
        file, check = QFileDialog.getOpenFileName(None, "Выберите изображение",
                                                  "", "Image files (*.jpg *.png *.svg)")
        if check:
            self.textfield.clear()
            comps = self.get_selected_items_with_confirm()
            if comps:
                self.thread = SetDesktopBackground(file)
                self.thread.host_list = comps

                self.thread.start_signal.connect(self.update_textfield)
                self.thread.progress_signal.connect(self.update_textfield)
                self.thread.finish_signal.connect(self.update_textfield)
                self.thread.finished.connect(self.thread.deleteLater)

                self.thread.start()

    def login_students(self) -> None:
        """
        Осуществляет удалённый вход в систему указанных в таблице пользователей
        """
        self.textfield.clear()
        comps = self.get_selected_items_with_confirm()
        if comps:
            self.thread = LoginStudents()
            self.thread.host_list = comps

            self.thread.start_signal.connect(self.update_textfield)
            self.thread.progress_signal.connect(self.update_textfield)
            self.thread.finish_signal.connect(self.update_textfield)
            self.thread.finished.connect(self.thread.deleteLater)

            self.thread.start()

    def end_session(self) -> None:
        """
        Вызывает контекстное меню для удалённого выхода из сессии на хостах
        """
        self.end_session_menu = QMenu()
        for elem in ('Полное завершение сеанса', 'Переключение пользователя'):
            text_action = QAction(elem, self)
            text_action.triggered.connect(self.on_end_session_menu_elem_select)
            self.end_session_menu.addAction(text_action)
        self.end_session_menu.exec(self.cursor().pos())

    def on_end_session_menu_elem_select(self) -> None:
        """
        Выполняет завершение активной сессии на хостах.
        В зависимости от выбранного пункта меню, завершение полное или с сохранением сеанса
        """
        self.textfield.clear()
        comps = self.get_selected_items_with_confirm()
        if comps:
            # False относилось к блокировке сеанса - см. end_session
            # self.thread = EndSession(True if self.sender().text().strip() == 'Полное завершение сеанса' else False)
            self.thread = EndSession(True)
            self.thread.host_list = comps
            self.thread.start_signal.connect(self.update_textfield)
            self.thread.progress_signal.connect(self.update_textfield)
            self.thread.finish_signal.connect(self.update_textfield)
            self.thread.finished.connect(self.thread.deleteLater)

            self.thread.start()

    def reboot_hosts(self) -> None:
        """
        Вызывает перезапуск хостов
        """
        self.textfield.clear()
        comps = self.get_selected_items_with_confirm()
        if comps:
            self.thread = SSHCommandInThreads(comps, 'echo reboot | at now', True)

            self.thread.start_signal.connect(self.update_textfield)
            self.thread.progress_signal.connect(self.update_textfield)
            self.thread.finished.connect(self.thread.deleteLater)

            self.thread.start()

    # def settings(self) -> None:
    #     """
    #     Открывает окно настроек
    #     @return: None
    #     """
    #     if self.settings_window.isVisible():
    #         logging.info("Закрыты настройки")
    #         self.settings_window.hide()
    #     else:
    #         # self.settings_window.show()
    #         current_user_groups = subprocess.run(["groups"], shell=True, capture_output=True).stdout.decode()
    #         if "wheel" in current_user_groups.split():
    #             logging.info("Открыты настройки")
    #             self.settings_window.show()
    #         else:
    #             logging.info("Пользователь не в группе wheel")
    #             dlg = QMessageBox(self)
    #             dlg.setWindowTitle("Ошибка")
    #             dlg.setText("Недостаточно прав")
    #             button = dlg.exec()
    #             if button == QMessageBox.Ok:
    #                 return None
    #
    def help(self) -> None:
        """
        Открывает справку
        """
        if self.help_window.isVisible():
            logging.info("Закрыта справка")
            self.help_window.hide()
        else:
            logging.info("Открыта справка")
            self.help_window.show()

    def closeEvent(self, event):
        event.accept()
        self.close()

    def close(self) -> None:
        """
        Закрывает окно приложения с сохранением всех его параметров для последующей загрузки
        """
        run_command(f'py-ini-config set {config_path}/ccs.conf Main xcoord {self.x()}')
        run_command(f'py-ini-config set {config_path}/ccs.conf Main ycoord {self.y()}')
        run_command(f'py-ini-config set {config_path}/ccs.conf Main windowwidth {self.width()}')
        run_command(f'py-ini-config set {config_path}/ccs.conf Main windowheight {self.height()}')
        settings_tab_right_panel_splitter_sizes = self.tab2.right_panel_splitter.sizes()
        run_command(f'py-ini-config set {config_path}/ccs.conf Main settings_tab_right_widget_height1 '
                    f'{settings_tab_right_panel_splitter_sizes[0]}')
        run_command(f'py-ini-config set {config_path}/ccs.conf Main settings_tab_right_widget_height2 '
                    f'{settings_tab_right_panel_splitter_sizes[1]}')
        logging.info(f'Установлены габариты окна: {self.width()}x{self.height()}+{self.x()}+{self.y()}')
        logging.info(f'Установлены размеры виджетов в сплиттере вкладки настроек: '
                     f'{settings_tab_right_panel_splitter_sizes}')
        logging.info("Приложение завершило работу")
        self.app.exit(0)
